#include "heightmap.h"
#include "imageloader.h"


Heightmap::Heightmap()
{
	heightmapVertColorsVA = 0x0;
	heightmapTexCoordsVA = 0x0;
	heightmapNormalsVA = 0x0;
	heightmapVerticesVA = 0x0;
	heightmapIndicesVA = 0x0;

	heightmapWidth = heightmapLength = 0;

	fb = 0x0;
	color_rb = 0x0;

	heightScale = 100.0f;
}

Heightmap::~Heightmap()
{
}

//void Heightmap::loadFromFile(string filename)
//{
//}

//void Heightmap::setTexture(GLuint texhandle)
//{
//	texHeightmap = texhandle;
//}

void Heightmap::loadHeightmap(const std::string& filename)
{
	std::vector<unsigned char> image;
	unsigned long width, height;	
	int error = ImageLoader::loadImagePNG(image, width, height, filename);

	//if there's an error, display it
	if(error != 0)
	{
		//std::cout << "PicoPNG error: " << error << std::endl;
		//if(error == 48) std::cout << filename.c_str() <<  " missing?" << std::endl;//the given data is empty
	}
	else
	{
		heightmapWidth = width;
		heightmapLength = height;
		heights = vector< vector<float> >( heightmapWidth, vector<float>(heightmapLength, 0.0f ) );

		// Read pixeldata stream into a 2x2 array for easier access.
		size_t w = width-1;
		size_t h = height-1;
		size_t i = 0;
		for(size_t y=0; y<height; y++)
		{
			for(size_t x=0; x<width; x++)
			{
				unsigned char r = image[i++];
				unsigned char g = image[i++];
				unsigned char b = image[i++];
				unsigned char a = image[i++];
				heights[x][y] = heightScale * ( float(r)/ 255.0f );
			}
		}

	}
}

void Heightmap::loadNormalmap(const std::string& filename)
{
	std::vector<unsigned char> image;
	unsigned long width, height;	
	int error = ImageLoader::loadImagePNG(image, width, height, filename);


	//if there's an error, display it
	if(error != 0)
	{
		//std::cout << "PicoPNG error: " << error << std::endl;
		//if(error == 48) std::cout << filename.c_str() <<  " missing?" << std::endl;//the given data is empty
	}
	else
	{
		// all ok
		for(size_t i= 0; i < image.size(); i+=4)
		{
			normals.push_back( float(image[i+0])/255.0f );
			normals.push_back( float(image[i+1])/255.0f );
			normals.push_back( float(image[i+2])/255.0f );
			// ignore alpha
		}

	}
}

GLuint Heightmap::createVBO(const void* data, int dataSize, GLenum target, GLenum usage)
{
    GLuint id = 0;  // 0 is reserved, glGenBuffersARB() will return non-zero id if success

    glGenBuffersARB(1, &id);                        // create a vbo
    glBindBufferARB(target, id);                    // activate vbo id to use
    glBufferDataARB(target, dataSize, data, usage); // upload data to video card

    // check data size in VBO is same as input array, if not return 0 and delete VBO
    int bufferSize = 0;
    glGetBufferParameterivARB(target, GL_BUFFER_SIZE_ARB, &bufferSize);
    
	std::cout << "made vbo, size: << " << bufferSize/1024 << " kb " << std::endl;

	if(dataSize != bufferSize)
    {
        glDeleteBuffersARB(1, &id);
        id = 0;
        std::cout << "[createVBO()] Data size is mismatch with input array\n";
    }

    return id;      // return VBO id
}

void Heightmap::createVertexBufferData()
{
	vertColors = vector<GLfloat> ( 3 * heightmapWidth*heightmapLength, 0.0f );
	texCoords = vector<GLfloat>( 2 * heightmapWidth*heightmapLength,  0.0f );
	//normals = vector<GLfloat> ( 3 * heightmapWidth*heightmapLength, 0.0f );
	vertices = vector<GLfloat> ( 3 * heightmapWidth*heightmapLength,  0.0f );
	size_t texCoordIdx = 0;
	size_t vertIdx = 0;
	size_t normIdx = 0;

	// Vertices & normals & texcoords:
	for(size_t z=0; z<heightmapLength; z++)
	{
		for(size_t x=0; x<heightmapWidth; x++)
		{
			float middleX = ( float(heightmapWidth)-1.0f) / 2.0f;
			float middleZ = ( float(heightmapLength)-1.0f) / 2.0f;
			vertices[vertIdx++] = float(x) - middleX;
			vertices[vertIdx++] = heights[x][z];
			vertices[vertIdx++] = float(z) - middleZ;

			texCoords[texCoordIdx++] = (float) x / (heightmapWidth-1);
			texCoords[texCoordIdx++] = (float) z / (heightmapLength-1);
		}
	}
	// Eof vertices.
	
	//copy normals into colors...
	//for(size_t i=0; i<vertColors.size(); i++)
	//	vertColors[i] = 0.8f;

	size_t idx = 0;
	for(size_t x = 0; x<heightmapWidth; x++)
		for(size_t z = 0; z < heightmapLength; z++)
		{
			vertColors[idx + 0] = float(x) / float(heightmapWidth);
			vertColors[idx + 1] = 0;
			vertColors[idx + 2] = float(z) / float(heightmapLength);
			idx+=3;
		}

	size_t numIndices = (heightmapWidth*2) * (heightmapLength - 1) + (heightmapLength -2 );
	indices = vector<GLuint>( numIndices, 0);
	
	size_t index = 0;
	for(size_t z=0; z<heightmapLength-1; z++)
	{
		size_t x = 0;
		if( z % 2 == 0)
		{
			// Even row go left
			for(x=0; x<heightmapWidth; x++)
			{
				// column-major array ordering
				indices[index++] = x + (z * heightmapWidth); // bottom
				indices[index++] = x + (z * heightmapWidth) + heightmapWidth; // top, 1 up Z-wise
			}

			// not last row? then make degenerate vert
			if( z != heightmapLength-2)
			{
				indices[index++] = (--x) + (z * heightmapWidth); // negative X?
			}
		}
		else
		{
			// Odd row go right
			for(x=heightmapWidth-1; x != -1; x--)
			{
				indices[index++] = x + (z * heightmapWidth); // bottom
				indices[index++] = x + (z * heightmapWidth) + heightmapWidth; // top, 1 up Z-wise
			}

			// not last row? then make degenerate vert
			if( z != heightmapLength-2)
			{
				// notice postinc of x value from -1 to 0.
				indices[index++] = ++x + (z * heightmapWidth);
			}
		}
	}

	heightmapVertColorsVA = createVBO(&vertColors[0],sizeof(GLfloat) * vertColors.size(),GL_ARRAY_BUFFER,GL_STATIC_DRAW);
	heightmapTexCoordsVA = createVBO(&texCoords[0],sizeof(GLfloat) * texCoords.size(),GL_ARRAY_BUFFER,GL_STATIC_DRAW);
	heightmapNormalsVA = createVBO(&normals[0],sizeof(GLfloat) * normals.size(),GL_ARRAY_BUFFER,GL_STATIC_DRAW);
	heightmapVerticesVA = createVBO(&vertices[0],sizeof(GLfloat) * vertices.size(),GL_ARRAY_BUFFER,GL_STATIC_DRAW);
	heightmapIndicesVA = createVBO(&indices[0],sizeof(GLfloat) * indices.size(),GL_ELEMENT_ARRAY_BUFFER,GL_STATIC_DRAW);

	size_t vertColorKiloBytes = sizeof(GLfloat) * vertColors.size() / 1024;
	size_t texCoordKiloBytes = sizeof(GLfloat) * texCoords.size() / 1024;
	size_t normalsKiloBytes = sizeof(GLfloat) * normals.size() / 1024;
	size_t vertsKiloBytes = sizeof(GLfloat) * vertices.size() / 1024;
	size_t indiceKiloBytes = sizeof(GLuint) * indices.size() / 1024;
	std::cout << "Created heightmap array data: " << std::endl
		<< " \t\tvertcolors(KB) \t  " << vertColorKiloBytes << std::endl
		<< " \t\ttexcoords(KB) \t  " << texCoordKiloBytes << std::endl
		<< " \t\tnormals(KB) \t  " << normalsKiloBytes << std::endl
		<< " \t\tvertices(KB) \t  " << vertsKiloBytes << std::endl
		<< " \t\tindices(KB) \t  " << indiceKiloBytes << std::endl
	<< " \ttotal (MB) = \t  " << (vertColorKiloBytes+texCoordKiloBytes+normalsKiloBytes+vertsKiloBytes+indiceKiloBytes)/1024 << std::endl;


	initFBO(); // move to init()
}

void Heightmap::render()
{
	glColor3f(.2f, .2f, .2f);
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D, texHeightmap);

	glEnable(GL_LIGHTING);
	//glDisable(GL_LIGHTING); glColor3f(1.0f, 1.0f, 1.0f);

	const int immidiate = 0;
	const int vertexBufferObject = 1;
	const int vertexArrays = 2;

	int renderMode = vertexBufferObject;

	switch(renderMode)
	{
	case immidiate:
			glBegin(GL_TRIANGLE_STRIP);
				for(size_t i=0; i < indices.size(); i++)
				{
					size_t vertIdx = 3 * indices[i];
					size_t texIdx = 2 * indices[i];
					glTexCoord2f( texCoords[texIdx+0], texCoords[texIdx+1] );
					glNormal3f( normals[vertIdx+0], normals[vertIdx+1], normals[vertIdx+2] );
					glVertex3f( vertices[vertIdx+0], vertices[vertIdx+1], vertices[vertIdx+2] ); 
				}
			glEnd();
			break;
	case vertexBufferObject:
 			glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT); // so we dont have to do all the disable calls
	

			//glEnableClientState(GL_COLOR_ARRAY);
			glEnableClientState(GL_VERTEX_ARRAY);
			glEnableClientState(GL_NORMAL_ARRAY);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			glEnableClientState(GL_INDEX_ARRAY);

			//glBindBuffer(GL_ARRAY_BUFFER, heightmapVertColorsVA);
			//glColorPointer(3, GL_FLOAT, 0 , 0);

			glBindBuffer(GL_ARRAY_BUFFER, heightmapNormalsVA);
			glNormalPointer(GL_FLOAT, 0 , NULL); // normal is always XYZ

			glBindBuffer(GL_ARRAY_BUFFER, heightmapTexCoordsVA);
			glTexCoordPointer(2, GL_FLOAT, 0, NULL);

			glBindBuffer(GL_ARRAY_BUFFER, heightmapVerticesVA);
			glVertexPointer(3, GL_FLOAT, 0 , NULL);
		
			//glDrawRangeElements(GL_TRIANGLE_STRIP,
			//	0,  heightmapLength*heightmapWidth, indices.size(), 
			//	GL_UNSIGNED_INT, 0 );

			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, heightmapIndicesVA); // no need to point to indices. We spec start end with draw:

			glDrawRangeElements(GL_TRIANGLE_STRIP,			// draw mode
				0, (heightmapWidth)*(heightmapLength),		// minimum index, maximum index
				indices.size()	,							// number of primitives to draw
				GL_UNSIGNED_INT,							// indice datatype
				NULL );								// indice array

			// ? Important both BUFFER types (data and index) are turned off before using VA?
			//glBindBuffer(GL_ARRAY_BUFFER, 0 ); // 
			
			
			//glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
			
			//glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0 ); 

			

			// Make sure to unbind if normal vertex arrays are to be used.
			glBindBuffer(GL_ARRAY_BUFFER, 0 );
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0 ); // <- IMPORTANT!!!
			
			//glDisableClientState(GL_VERTEX_ARRAY);
			//glDisableClientState(GL_NORMAL_ARRAY);
			//glDisableClientState(GL_TEXTURE_COORD_ARRAY);
			//glDisableClientState(GL_INDEX_ARRAY);

			glPopClientAttrib(); // <- wtF? doesnt deactivate buffer bindings over?

			break;
	case vertexArrays:
			//glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);
			
			//glEnableClientState(GL_COLOR_ARRAY);
			glEnableClientState(GL_VERTEX_ARRAY);
			glEnableClientState(GL_NORMAL_ARRAY);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			

			//glColorPointer(3, GL_FLOAT, 0, colors);
			glTexCoordPointer(2, GL_FLOAT, 0, &texCoords[0]);
			glNormalPointer(GL_FLOAT, 0 , &normals[0]);
			glVertexPointer(3, GL_FLOAT, 0 , &vertices[0]);
 
			glDrawRangeElements(GL_TRIANGLE_STRIP,			// draw mode
				0, (heightmapWidth)*(heightmapLength),		// minimum index, maximum index
				indices.size()	,							// number of primitives to draw
				GL_UNSIGNED_INT,							// indice datatype
				&indices[0] );								// indice array

			glDisableClientState(GL_VERTEX_ARRAY);
			glDisableClientState(GL_NORMAL_ARRAY);
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
			//glDisableClientState(GL_COLOR_ARRAY);

			//glPopClientAttrib();
			break;
	default:
		break;
	}


	glDisable(GL_TEXTURE_2D);

	
	bool showNormals = 0;
	// Show Normals
	if(showNormals)
	{
		glDisable(GL_LIGHTING);
		glColor3f(1,1,0);
		glBegin(GL_LINES);
		for(size_t i=0; i < 3*heightmapWidth*heightmapLength; i+=3)
		{
			glVertex3f( vertices[i+0], vertices[i+1], vertices[i+2] );

			glVertex3f( vertices[i+0] + 0.5f*normals[i+0], 
						vertices[i+1] + 0.5f*normals[i+1], 
						vertices[i+2] + 0.5f*normals[i+2] );
		}
		glEnd();
	}


}

void Heightmap::enableRenderBuffer()
{
  GLint viewport[4];							// Where The Viewport Values Will Be Stored
  glGetIntegerv(GL_VIEWPORT, viewport);			// Retrieves The Viewport Values (X, Y, Width, Height)  
  oldScreenWidth = viewport[2];
  oldScreenHeight = viewport[3];
  glViewport (0, 0, fboWidth, fboHeight);

  //glDrawBuffer (GL_NONE);
  //glReadBuffer(GL_NONE);

  glDisable(GL_TEXTURE_2D);
  glDisable(GL_BLEND);
  glEnable(GL_DEPTH_TEST);

  //glBindTexture(GL_TEXTURE_2D, 0);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, color_rb);
}

void Heightmap::disableRenderBuffer()
{
  // Unbind FBO
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
  
  // Set viewport back
  glViewport (0, 0, oldScreenWidth,oldScreenHeight);
}

void Heightmap::renderPickingColors()
{
	enableRenderBuffer();

	glDisable(GL_LIGHTING);
  
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  glClear(GL_COLOR_BUFFER_BIT); // | GL_DEPTH_BUFFER_BIT

			glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT); // so we dont have to do all the disable calls
			
			glEnableClientState(GL_COLOR_ARRAY);
			glEnableClientState(GL_VERTEX_ARRAY);
			//glEnableClientState(GL_NORMAL_ARRAY);
			//glEnableClientState(GL_TEXTURE_COORD_ARRAY);

			glBindBuffer(GL_ARRAY_BUFFER, heightmapVertColorsVA);
			glColorPointer(3, GL_FLOAT, 0 , 0);

			//glBindBuffer(GL_ARRAY_BUFFER, heightmapTexCoordsVA);
			//glTexCoordPointer(2, GL_FLOAT, 0, 0);

			//glBindBuffer(GL_ARRAY_BUFFER, heightmapNormalsVA);
			//glNormalPointer(GL_FLOAT, 0 , 0); // normal is always XYZ

			glBindBuffer(GL_ARRAY_BUFFER, heightmapVerticesVA);
			glVertexPointer(3, GL_FLOAT, 0 , 0);

			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, heightmapIndicesVA); // no need to point to indices. We spec start end with draw:
			glDrawRangeElements(GL_TRIANGLE_STRIP,
				0,  heightmapLength*heightmapWidth, indices.size(), 
				GL_UNSIGNED_INT, 0 );
			
			// Make sure to unbind if normal vertex arrays are to be used.
			glBindBuffer(GL_ARRAY_BUFFER, 0 );
			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
			
			glPopClientAttrib();

		if(isPickUpdated)
		{
			calcPickedPoint();
		}
	disableRenderBuffer();
}

//void calcNormalFromHeightmap()
//{
//			// Calc vertex normal based on heightmap gradient
//			float v0x = (float)x; float v0y = heights[x][z]; float v0z = (float)z;
//			float v1x = (float)x  + 1; float v1y = heights[x][z]; float v1z = (float)z;
//			float v2x = (float)x; float v2y = heights[x][z]; float v2z = (float)z + 1;
//
//			if(x != heightmapWidth-1){
//				 v1y = heights[x+1][z];
//			}
//			if(z != heightmapLength-1){
//				 v2y = heights[x][z+1];
//			}
//			float e0x = v1x - v0x; float e0y = v1y - v0y; float e0z = v1z - v0z;
//			float e1x = v2x - v0x; float e1y = v2y - v0y; float e1z = v2z - v0z;
//			// Cross the two vectors e0 and e1
//			// X=YZ-ZY <- :) "EX-yzy, its easy"
//			// Y=ZX-XZ
//			// Z=XY-YX
//			float nx0 = e0y*e1z - e0z*e1y; float ny0 = e0z*e1y - e0x*e1z; float nz0 = e0x*e1x - e0y*e1x;
//			// Normalize
//			float len0 = sqrt(nx0*nx0 + ny0*ny0 + nz0*nz0);
//			nx0 /= len0; ny0 /= len0; nz0 /= len0;
//			// Compensating for multiplying with wrong handedness:
//			
//			// dont store calced. use normalmap
//			//normals[normIdx++] = -nx0;
//			//normals[normIdx++] = -ny0;
//			//normals[normIdx++] = -nz0;
//}

void Heightmap::initFBO()
{
	int color_bufs = 0;
	int max_rb_size = 0;
	glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &color_bufs );
	glGetIntegerv(GL_MAX_RENDERBUFFER_SIZE_EXT, &max_rb_size );
	std::cout << "colorbufs supported: " << color_bufs  << " max rb size: " << max_rb_size <<  std::endl;

	fboWidth = 256;
	fboHeight = 256;
  glGenFramebuffersEXT(1, &fb);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb); // Switch to framebuffer so things
	
	glGenRenderbuffersEXT(1, &color_rb); // Make color render buffer
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, color_rb);
	glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_RGB8, fboWidth, fboHeight); // 8-bits RGB no Alpha
	
	//define a renderbuffer
	glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, color_rb);
	//or define a texturebuffer
//glFramebufferTexture2DEXT(GLenum target,
//                          GLenum attachmentPoint,
//                          GLenum textureTarget,
//                          GLuint textureId,
//                          GLint  level)

	//GLuint tex_id;
	//glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, tex_id, color_rb);
	//------
  // Go back to regular frame buffer rendering
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

	// Does the GPU support current FBO config?
	GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	switch(status)
	{
	case GL_FRAMEBUFFER_COMPLETE_EXT: std::cout << "Framebuffer OK" << std::endl; break;
		default:
			std::cout << __FILE__ << ": Framebuffer error";
			exit(0);
	}
}

void Heightmap::releaseFBO()
{
	glDeleteRenderbuffersEXT(1, &color_rb);
  glDeleteFramebuffersEXT(1, &fb);
  glDeleteBuffers(1, &fb);
}

void Heightmap::setPickPoint(int x, int y)
{
  mPickX = x;
  mPickY = y;
  isPickUpdated = true;
}

void Heightmap::calcPickedPoint()
{
	GLfloat color[3];
	// because FBO isnt same scale as window, normalize mousecoordinates, then scale by FBO-buffer size.
	int mouseScaleX = int( float(fboWidth)*(float(mPickX))/float(oldScreenWidth) );			
	int mouseScaleY = int( float(fboHeight)*(float(mPickY))/float(oldScreenHeight) );
	glReadPixels( mouseScaleX, fboHeight-mouseScaleY, 1, 1, GL_RGB, GL_FLOAT, color); // read 1 pixel at mousecoords.
	int ix = int( color[0]*float(heightmapWidth-1) );
	int iz = int( color[2]*float(heightmapLength-1) );

	int xlut = std::min<int>(heightmapWidth,  std::max<int>(ix,0) );
	int zlut = std::min<int>(heightmapLength, std::max<int>(iz,0) );

	float ypos = heights[zlut][xlut];
	float heightAtPoint = heights[zlut][xlut];
	float middleX = float(heightmapWidth-1)/2.0f;
	float middleZ = float(heightmapLength-1)/2.0f;
	pickedPoint = Vec3f( (float)-middleZ + color[2]*heightmapLength, (float)ypos, (float)-middleX + color[0]*heightmapWidth );
	isPickUpdated = false;
}

Vec3f Heightmap::getPickedPoint()
{
	return pickedPoint;
}